using System;
using System.Collections.Generic;
using System.Collections;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Text;

namespace needle.Services.astra.Common
{
	/// <summary>
	/// Regular expression searcher with support for results inversion.
	/// </summary>
	public class TextMatchList : ReadOnlyCollection<TextMatch>
	{
		/// <summary>
		/// Constructor.
		/// Initializes <see cref="TextMatch"/> list (calls <see cref="Init"/>).
		/// </summary>
		/// <param name="fullText">See <see cref="FullText"/>.</param>
		/// <param name="matchPattern">See <see cref="MatchPattern"/>.</param>
		/// <param name="regexOptions">See <see cref="RegexOptions"/>.</param>
		/// <param name="resultInversion">See <see cref="ResultInversion"/>.</param>
		/// <param name="allowEmptyResult">See <see cref="AllowEmptyResult"/>.</param>
		public TextMatchList(string fullText, string matchPattern, RegexOptions regexOptions,
			bool resultInversion, bool allowEmptyResult)
			: base(new List<TextMatch>())
		{
			this._fullText = fullText;
			this._matchPattern = matchPattern;
			this._regexOptions = regexOptions;
			this._resultInversion = resultInversion;
			this._allowEmptyResult = allowEmptyResult;
			Init();
		}

		/// <summary>
		/// Full text to search in.
		/// </summary>
		/// <remarks>set: If value changes, calls <see cref="Init"/>.</remarks>
		public string FullText
		{
			get { return _fullText; }
			set
			{
				if (this._fullText == value)
					return;
				this._fullText = value;
				Init();
			}
		}
		/// <summary>
		/// Regular expression pattern to search for.
		/// </summary>
		/// <remarks>set: If value changes, calls <see cref="Init"/>.</remarks>
		public string MatchPattern
		{
			get { return _matchPattern; }
			set
			{
				if (this._matchPattern == value)
					return;
				this._matchPattern = value;
				Init();
			}
		}
		/// <summary>
		/// Regular expression options.
		/// </summary>
		/// <remarks>set: If value changes, calls <see cref="Init"/>.</remarks>
		public RegexOptions RegexOptions
		{
			get { return _regexOptions; }
			set
			{
				if (this._regexOptions == value)
					return;
				this._regexOptions = value;
				Init();
			}
		}
		/// <summary>
		/// Whether to invert found results - all except normal regular expression matches.
		/// </summary>
		/// <remarks>set: If value changes, calls <see cref="Init"/>.</remarks>
		public bool ResultInversion
		{
			get { return _resultInversion; }
			set
			{
				if (this._resultInversion == value)
					return;
				this._resultInversion = value;
				Init();
			}
		}
		/// <summary>
		/// Whether to allow empty regular expression matches.
		/// </summary>
		/// <remarks>set: If value changes, calls <see cref="Init"/>.</remarks>
		public bool AllowEmptyResult
		{
			get { return _allowEmptyResult; }
			set
			{
				if (this._allowEmptyResult == value)
					return;
				this._allowEmptyResult = value;
				Init();
			}
		}

		/// <summary>
		/// Replaces all matches in <see cref="FullText"/> with <paramref name="replaceText"/>.
		/// </summary>
		/// <param name="replaceText">Text used to replace matches.</param>
		/// <returns>Replaced text.</returns>
		public string Replace(string replaceText)
		{
			string fullText = this.FullText;
			StringBuilder text = new StringBuilder();
			int previousIndex = 0;
			int length;
			foreach (TextMatch match in this)
			{
				length = match.Index - previousIndex;
				if (length > 0)
					text.Append(fullText.Substring(previousIndex, length));
				previousIndex = match.Index + match.Length;
				text.Append(replaceText);
			}
			if (previousIndex < fullText.Length)
				text.Append(fullText.Substring(previousIndex));
			return text.ToString();
		}

		/// <summary>
		/// Initializes <see cref="TextMatch"/> list.
		/// </summary>
		/// <remarks>
		/// Uses <see cref="Regex.Matches"/> and transforms <see cref="Match"/>es to <see cref="TextMatch"/>es.
		/// If <see cref="ResultInversion"/> is false, matches are transformed to text sections of <see cref="FullText"/> between matches.
		/// </remarks>
		private void Init()
		{
			MatchCollection matches = Regex.Matches(this.FullText, this.MatchPattern, this.RegexOptions);
			this.Items.Clear();
			int count = matches.Count;
			Match match, previousMatch = null;
			TextMatch textMatch;
			for (int i = 0; i <= count; i++)
			{
				if (i < count)
				{
					match = matches[i];
					if (!this.AllowEmptyResult && match.Length == 0)
						continue;
				}
				else
				{
					match = null;
				}
				if (this.ResultInversion)
				{
					textMatch = new TextMatch(this.FullText, previousMatch, match);
					previousMatch = match;
					if (textMatch.IsValid && (this.AllowEmptyResult || textMatch.Length > 0))
						this.Items.Add(textMatch);
				}
				else if (match != null)
				{
					this.Items.Add(new TextMatch(this.FullText, match));
				}
			}
		}

		/// <summary>
		/// See <see cref="FullText"/>.
		/// </summary>
		private string _fullText;
		/// <summary>
		/// See <see cref="MatchPattern"/>.
		/// </summary>
		private string _matchPattern;
		/// <summary>
		/// See <see cref="RegexOptions"/>.
		/// </summary>
		private RegexOptions _regexOptions;
		/// <summary>
		/// See <see cref="ResultInversion"/>.
		/// </summary>
		private bool _resultInversion;
		/// <summary>
		/// See <see cref="AllowEmptyResult"/>.
		/// </summary>
		private bool _allowEmptyResult;
	}
}
